/*
 * Copyright (C) 2016 Evgeniy Egorov
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package ru.apertum.qsystem.client;

import lombok.extern.log4j.Log4j2;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import ru.apertum.qsystem.common.NetCommander;
import ru.apertum.qsystem.common.exceptions.QException;
import ru.apertum.qsystem.common.model.INetProperty;
import ru.apertum.qsystem.server.ServerProps;
import ru.apertum.qsystem.server.model.QProperty;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;


/**
 * Это класс предоставления параметров, которые хранятся в БД и могут использоваться сервером и админкой напрямую. Это класс запросит по сети параметры.
 * Используйте метод load() для получения после инициализации параметров по сети. Именно этот класс надо использовать для доступа к настройкам в компанентах
 * системы.
 *
 * @author Evgeniy Egorov
 */
@Log4j2
public class QProperties {

    private static Logger logTrace = LogManager.getLogger("adminProperties");

    public static final String SECTION_SERVER = "SERVER";
    public static final String SECTION_WELCOME = "WELCOME";
    public static final String SECTION_CLIENT = "OPERATOR";
    public static final String SECTION_REPORTS = "REPORTS";
    public static final String SECTION_UPDATER = "UPDATER";
    public static final String SECTION_SYSTEM = "SYSTEM";
    public static final String SECTION_RECEPTION = "RECEPTION";
    public static final String SECTION_MAIN_TABLO = "MAIN_TABLO";
    public static final String BUTTONS_QR_CODE = "BUTTONS_QR_CODE";

    private LinkedHashMap<String, ServerProps.Section> properties = new LinkedHashMap<>();

    private QProperties() {
    }

    public static QProperties get() {
        return QPropertiesHolder.INSTANCE;
    }

    private static class QPropertiesHolder {

        private static final QProperties INSTANCE = new QProperties();
    }

    /**
     * Загрузить параметры если еще не загружены.
     *
     * @param netProperty параметры для загрузки
     */
    public void load(INetProperty netProperty) throws QException {
        load(netProperty, false);
    }

    /**
     * Загрузить параметры если еще не загружены.
     *
     * @param netProperty параметры для загрузки
     * @param force       если true, то загрузить принудительно даже если уже были загружены
     */
    public void load(INetProperty netProperty, boolean force) throws QException {
        if (force || properties == null || properties.isEmpty()) {
            properties = NetCommander.getProperties(netProperty);
            this.netProperty = netProperty;
        }
    }

    /**
     * Настройки по которым происходит загрузка. Инициализируются при первой загрузке.
     */
    private INetProperty netProperty = null;

    /**
     * Загрузить параметры если еще не загружены.
     */
    public Map<String, ServerProps.Section> reload() throws QException {
        if (netProperty != null) {
            properties = NetCommander.getProperties(netProperty);
        }
        return properties;
    }

    /**
     * Может быть NULL, если нет такой сейции.
     *
     * @param section имя секции или NULL.
     * @return список параметров.
     */
    public Map<String, QProperty> getSectionProps(String section) {
        return properties.get(section).getProperties();
    }

    /**
     * Получить существующую секцию.
     *
     * @param section имя существующей секции
     * @return существующая секция с именем или NULL
     */
    public ServerProps.Section getSection(String section) {
        return properties.get(section);
    }

    /**
     * Добавить существующую секцию.
     *
     * @param section новая секция.
     * @return Та же самая новая секция.
     */
    public ServerProps.Section addSection(ServerProps.Section section) {
        return properties.put(section.getName(), section);
    }

    /**
     * Получить все параметры списком.
     *
     * @return список всех параметров из БД
     */
    public List<QProperty> getAllProperties() {
        final ArrayList<QProperty> col = new ArrayList<>();
        properties.values().forEach(sec -> col.addAll(sec.getProperties().values()));
        return col;
    }

    /**
     * Получить все настройки, что лежат в БД. Только для чтения.
     *
     * @return список секций с параметрами.
     */
    public Collection<ServerProps.Section> getSections() {
        return properties.values();
    }

    /**
     * Получает параметр по имени из секции.
     *
     * @param section название секции
     * @param key     название параметра
     * @return запрашиваемый параметер. Может быть NULL если не нашелся параметер.
     */
    public QProperty getProperty(String section, String key) {
        triceParam(section + "->" + key);
        if (key == null) {
            throw new IllegalArgumentException("Key must be not NULL");
        }
        final ServerProps.Section secmap = getSection(section);
        if (secmap == null) {
            return null;
        }
        if (!secmap.getProperties().containsKey(key)) {
            return null;
        }
        return secmap.getProperties().get(key);
    }

    /**
     * Получает параметр по имени из секции.
     *
     * @param section  название секции
     * @param key      название параметра
     * @param defValue значение по умолчанию.
     * @return запрашиваемый параметер. Или значение по умолчанию.
     */
    public String getProperty(String section, String key, String defValue) {
        triceParam(section + "->" + key + "[=" + defValue + "]");
        if (key == null) {
            return defValue;
        }
        final ServerProps.Section secmap = getSection(section);
        if (secmap == null) {
            return defValue;
        }
        if (!secmap.getProperties().containsKey(key)) {
            return defValue;
        }
        return secmap.getProperties().get(key).getValue();
    }

    /**
     * Получает параметр по имени из секции.
     *
     * @param section  название секции
     * @param key      название параметра
     * @param defValue значение по умолчанию.
     * @return запрашиваемый параметер. Или значение по умолчанию.
     */
    public int getProperty(String section, String key, int defValue) {
        final QProperty property = getProperty(section, key);
        return property == null ? defValue : property.getValueAsInt(defValue);
    }

    /**
     * Получает параметр по имени из секции.
     *
     * @param section  название секции
     * @param key      название параметра
     * @param defValue значение по умолчанию.
     * @return запрашиваемый параметер. Или значение по умолчанию.
     */
    public double getProperty(String section, String key, double defValue) {
        final QProperty property = getProperty(section, key);
        return property == null ? defValue : property.getValueAsDouble(defValue);
    }

    /**
     * Получает параметр по имени из секции.
     *
     * @param section  название секции
     * @param key      название параметра
     * @param defValue значение по умолчанию.
     * @return запрашиваемый параметер. Или значение по умолчанию.
     */
    public boolean getProperty(String section, String key, boolean defValue) {
        final QProperty property = getProperty(section, key);
        return property == null ? defValue : property.getValueAsBool(defValue);
    }

    /**
     * Получает параметр по имени из секции. Полуцчаем цвет.
     *
     * @param section  название секции
     * @param key      название параметра в формате "#FFFFFF", # - можно опустить.
     * @param defValue значение по умолчанию.
     * @return запрашиваемый параметер или null. Или значение по умолчанию.
     */
    public Color getColorProperty(String section, String key, Color defValue) {
        final QProperty property = getProperty(section, key);
        return property == null ? defValue : parseStringToColor(property.getValue(), defValue);
    }

    private Color parseStringToColor(String key, Color defValue) {
        if (key == null || key.isEmpty()) {
            return defValue;
        }
        return Color.decode("#" + key.replace("#", ""));
    }

    /**
     * Сохранить параметры на сервере.
     *
     * @param netProperty параметры для загрузки
     * @param propList    эти параметры сохранить
     */
    public Map<String, ServerProps.Section> save(INetProperty netProperty, List<QProperty> propList) throws QException {
        properties = NetCommander.saveProperties(netProperty, propList);
        return properties;
    }

    private final HashMap<String, Long> trace = new HashMap<>();

    private void triceParam(String str) {
        Long tm = trace.get(str);
        if (tm == null || 1000 * 60 * 60 < System.currentTimeMillis() - tm) {
            trace.put(str, System.currentTimeMillis());
            logTrace.info(str);
        }
    }
}
